tags:
- Cpp
Unions in C++
在 C++ 中,大多数人可能都知道 class
和 struct
的类类型,但是许多人会忽略 union
也是一种类类型,这是因为 C++ 中的联合体也可以拥有自己的成员函数(包括构造和析构函数)。但它的使用是特别的,因为其特性,联合体类 union
内每次只能有一个活跃的成员。
由于联合体需要保证类内任何一个成员都能够被访问到,所以联合体类型至少要和类内最大的数据成员的大小一样大,通常等于联合体类内最大的数据成员的大小。当我们访问联合体类时,其类内的每个数据成员都会像是类内唯一的数据成员。
union
的一个简单用例如下,我们用 union
定义了一个联合体类型 U
。其中有三个数据成员:
#include <iostream>
union U{ // public access by default
int u_i; // 4 bytes
short u_s; // 2 bytes, hold integer number no bigger than 65535
float u_f; // 4 bytes
}; // thus the size of U class is 4 bytes
int main(){
U u = {65536}; // initializes the first member u.u_i
// U u.u_i = {65536};
std::cout << u_i << std::endl
<< u_s << std::endl
<< u_f << std::endl;
return 0;
}
输出:
65536
0
9.18355e-41
上面的代码中,我们定义了一个包含三个数据成员的 union
类。在 union
中,所有的数据变量都共享一个地址,但是它们访问数据的方式和大小各不相同。我们初始化了第一个成员 u_i
,这意味着剩下的两个变量的状态在此时是未定义的。这也就不难理解为什么我们会得到如此奇怪的输出。
在一时刻, union
的内存布局如下所示:
因为 short
类型大小为两个字节,所以即使 short
和 int
都表示整型数,在赋值 65536
时,short
仍然不能访问高二字节地址的数据。而 float
数据的访问方式又与整型数有所不同,我们会得到不一样的结果。如果后面我们对 u_s
进行赋值,那么 u_i
的生命周期也就随之结束。
以上是联合体最常用的方式。这样做有什么好处?节省内存。
作为一种特殊的类, union
可以拥有自己的成员函数。但需要注意的是 union
不允许继承和派生,因此也不允许出现虚函数。union
中,拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数默认会被删除。除非显式地定义这些函数。
匿名联合体是匿名的,它不能有任何成员函数、静态数据成员而且所有的数据成员都需要是公有的。匿名联合体必须定义在作用域 (scope) 中,而且其成员不能与作用域中已声明的名字冲突。接着上面的例子,它的匿名联合体会是这样的:
#include <iostream>
namespace U {
long u_l;
union { // public access by default
int u_i; // 4 bytes
short u_s; // 2 bytes, holds integer numbers no bigger than 65535
float u_f; // 4 bytes
// long u_l; // not allowed
} static; // anonymous unions at namespace scope must be static
}
int main() {
U::u_i = 65536; // initializes the first member myspace::u_i
std::cout << U::u_i << std::endl
<< U::u_s << std::endl
<< U::u_f << std::endl;
return 0;
}
如果在一个函数的作用域中,匿名函数就是这样的:
#include <iostream>
int main() {
union { // public access by default
int u_i; // 4 bytes
short u_s; // 2 bytes, holds integer numbers no bigger than 65535
float u_f; // 4 bytes
};
u_i = 65536; // initializes u_i
std::cout << u_i << std::endl
<< u_s << std::endl
<< u_f << std::endl;
return 0;
}